/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package luft27.zorcon.ui;

import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.WorldWindow;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.layers.MarkerLayer;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.render.markers.*;
import java.util.ArrayList;
import java.util.List;
import luft27.propertytree.PropertyChangedListener;
import luft27.propertytree.PropertyNode;
import luft27.propertytree.PropertyTree;
import luft27.propertytree.PropertyValue;

/**
 *
 * @author amalikov
 */
public class Vehicle {
	
	public Vehicle(String root, WorldWindow wwd) {
		this.root = root;
		this.wwd = wwd;

		updateVehicleListener = new PropertyChangedListener() {
			@Override
			public void actionPerformed(PropertyValue value) {
				java.awt.EventQueue.invokeLater(new Runnable() {
					@Override
					public void run() {
						updateVehicle();
					}
				});
			}
		};

		updateRouteListener = new PropertyChangedListener() {
			@Override
			public void actionPerformed(PropertyValue value) {
				java.awt.EventQueue.invokeLater(new Runnable() {
					@Override
					public void run() {
						updateRoute();
					}
				});
			}
		};
		
		track = new ArrayList<>();
		
		lastFrom = null;
		lastTo = null;
	}
	
	public void bind() {
		PropertyNode v = PropertyTree.INSTANCE.getNode(root);
		v.getNode("/updatetrigger").addListener(updateVehicleListener);
		v.getNode("/route/current").addListener(updateRouteListener);
	}
	
	public void unbind() {
		PropertyNode v = PropertyTree.INSTANCE.getNode(root);
		v.getNode("/updatetrigger").removeListener(updateVehicleListener);
		v.getNode("/route/current").removeListener(updateRouteListener);
	}
	
	private void updateVehicle() {
		PropertyNode v = PropertyTree.INSTANCE.getNode(root);
			
		setVehicle(
			Position.fromDegrees(
				v.get("/position/latitude").asDouble(),
				v.get("/position/longitude").asDouble(), 
				v.get("/position/altitude").asDouble()), 
			v.get("/orientation/heading").asDouble());
		
		double alt0 = v.get("/route/current/offset").asDouble();
		
		setActiveLeg(
			Position.fromDegrees(
				v.get("/track/leg/from/latitude").asDouble(),
				v.get("/track/leg/from/longitude").asDouble(), 
				v.get("/track/leg/from/altitude").asDouble() + alt0), 
			Position.fromDegrees(
				v.get("/track/leg/to/latitude").asDouble(),
				v.get("/track/leg/to/longitude").asDouble(), 
				v.get("/track/leg/to/altitude").asDouble() + alt0));		
	}
	
	private void setVehicle(Position pos, double hdg) {
		MarkerAttributes attr = new BasicMarkerAttributes(Material.WHITE, BasicMarkerShape.ORIENTED_SPHERE, 1);
		attr.setHeadingMaterial(Material.GREEN);
		attr.setHeadingScale(2);
				
		List<Marker> markers = new ArrayList<>();
		markers.add(new BasicMarker(pos, attr, Angle.fromDegrees(hdg)));
		
		MarkerLayer layer = (MarkerLayer)wwd.getModel().getLayers().getLayerByName("Vehicle");
		layer.setMarkers(markers);
		
		wwd.redraw();
		
		updateTrack(pos);
	}
	
	private void setActiveLeg(Position from, Position to) {
		if (from.equals(lastFrom) && to.equals(lastTo))	// update optimization
			return;
		
		lastFrom = from;
		lastTo = to;
		
		List<Position> leg = new ArrayList<>();
		leg.add(from);
		leg.add(to);

		RenderableLayer layer = (RenderableLayer)wwd.getModel().getLayers().getLayerByName("ActiveLeg");
		layer.removeAllRenderables();
		addLegs(layer, leg, Material.YELLOW, 2, false, true);
		
		wwd.redraw();
	}
	
	private void updateTrack(Position pos) {
		if (!track.isEmpty() && getAbsoluteDistance(track.get(track.size() - 1), pos) < MIN_TRACK_DISTANCE)
			return;
		
		track.add(pos);
		
		RenderableLayer layer = (RenderableLayer)wwd.getModel().getLayers().getLayerByName("Track");
		layer.removeAllRenderables();
		addTrack(layer, track, Material.GREEN, 1, false, false);

		wwd.redraw();
	}
	
	private void updateRoute() {
		RenderableLayer layer = (RenderableLayer)wwd.getModel().getLayers().getLayerByName("Route");
		layer.removeAllRenderables();
		
		PropertyNode r = PropertyTree.INSTANCE.getNode(root + "/route/current");
		List<Position> route = getRoute(r);
		r.set("/offset", new PropertyValue(getOffset(route)));
		
		int patternSize = route.size() >= 6 ? 6 : route.size();
		List<Position> routeMain = route.subList(0, route.size() - patternSize);
		List<Position> routePattern = route.subList(route.size() - patternSize, route.size());

		addLegs(layer, applyOffset(routeMain, r.get("/offset").asDouble()), Material.RED, 1, false, true);
		addLegs(layer, applyOffset(routePattern, r.get("/offset").asDouble()), Material.MAGENTA, 1, false, true);
		
		if (routePattern.size() == 6) {
			List<Position> ga = new ArrayList<>();
			ga.add(routePattern.get(5));
			ga.add(routePattern.get(0));
			addLegs(layer, applyOffset(ga, r.get("/offset").asDouble()), Material.MAGENTA, 1, true, true);
		}
		
		addPointPlacemarks(layer, route);
		
		wwd.redraw();
	}

	private List<Position> getRoute(PropertyNode r) {
		List<Position> route = new ArrayList<>();
		
		boolean eof = false;
		for (int i = 0; !eof; ++i) {
			PropertyNode w = r.getNode("/wpt[" + i + "]");
			if (w.get("/present").asBool()) {
				route.add(Position.fromDegrees(
					w.get("/latitude").asDouble(), 
					w.get("/longitude").asDouble(), 
					w.get("/altitude").asDouble()));
			} else
				eof = true;
		}
		
		return route;
	}

	/**
	 * Determine altitude offset as the terrain elevation under the last point
	 */
	private double getOffset(List<Position> route) {
		if (!route.isEmpty()) {
			Position last = route.get(route.size() - 1);
			return wwd.getModel().getGlobe().getElevation(last.latitude, last.longitude);
		}
		return 0;
	}
	
	private List<Position> applyOffset(final List<Position> orig, double offset) {
		List<Position> corr = new ArrayList<>();
		for (Position pos : orig)
			corr.add(new Position(pos.latitude, pos.longitude, pos.elevation + offset));
		return corr;
	}
	
	private void addLegs(RenderableLayer layer, List<Position> pos, Material mat, double width, boolean stipple, boolean extrude) {
		BasicShapeAttributes attr = new BasicShapeAttributes();
		attr.setInteriorMaterial(mat);
		attr.setInteriorOpacity(0.25);
		attr.setOutlineWidth(width);
		attr.setOutlineMaterial(mat);
		
		if (stipple) {
			attr.setOutlineStipplePattern((short)0x5555);
			attr.setOutlineStippleFactor(5);
		}
		
		for (int i = 0; i < pos.size() - 1; ++i) {
			Position a = pos.get(i);
			Position b = pos.get(i + 1);

			Path leg = new Path(a, b);
			leg.setDrawVerticals(false);
			leg.setFollowTerrain(true);
			leg.setPathType(AVKey.GREAT_CIRCLE);
			leg.setExtrude(extrude);
			leg.setTerrainConformance(100);
			leg.setValue(AVKey.DISPLAY_NAME, getLegInfo(a, b));
			leg.setAttributes(attr);
			leg.setEnableBatchPicking(false);

			layer.addRenderable(leg);
		}
	}

	private void addTrack(RenderableLayer layer, List<Position> pos, Material mat, double width, boolean stipple, boolean extrude) {
		BasicShapeAttributes attr = new BasicShapeAttributes();
		attr.setInteriorMaterial(mat);
		attr.setInteriorOpacity(0.25);
		attr.setOutlineWidth(width);
		attr.setOutlineMaterial(mat);
		
		if (stipple) {
			attr.setOutlineStipplePattern((short)0x5555);
			attr.setOutlineStippleFactor(5);
		}
		
		Path leg = new Path(pos);
		leg.setDrawVerticals(false);
		leg.setFollowTerrain(true);
		leg.setPathType(AVKey.GREAT_CIRCLE);
		leg.setExtrude(extrude);
		leg.setTerrainConformance(100);
		leg.setAttributes(attr);
		leg.setEnableBatchPicking(false);

		layer.addRenderable(leg);
	}
	
	
	private String getLegInfo(Position a, Position b) {
		LatLon lla = new LatLon(a.getLatitude(), a.getLongitude());
		LatLon llb = new LatLon(b.getLatitude(), b.getLongitude());
		
		double initCourse = Position.greatCircleAzimuth(lla, llb).getDegrees();
		if (initCourse < 0)
			initCourse += 360;
			
		double range = Position.greatCircleDistance(lla, llb).getRadians() 
				* wwd.getModel().getGlobe().getRadius();
		
		return Integer.toString((int)Math.round(initCourse)) + "° " 
				+ Double.toString(Math.round(range / 100.0) / 10.0) + " km";
	}
	
	private void addPointPlacemarks(RenderableLayer layer, List<Position> waypoints) {
		int index = 0;
		for (Position pos : waypoints) {
            // Create and set an attribute bundle.
            ShapeAttributes attrs1 = new BasicShapeAttributes();
            attrs1.setInteriorMaterial(Material.RED);
            attrs1.setInteriorOpacity(0.7);
            attrs1.setEnableLighting(true);
            attrs1.setOutlineMaterial(Material.RED);
            attrs1.setOutlineWidth(2d);
            attrs1.setDrawInterior(true);
            attrs1.setDrawOutline(false);
			
            Ellipsoid sphere = new Ellipsoid(pos, 60, 60, 60);
            sphere.setAltitudeMode(WorldWind.CLAMP_TO_GROUND);
            sphere.setAttributes(attrs1);
            sphere.setValue(AVKey.DISPLAY_NAME, Integer.toString(index) + ") " + Integer.toString((int) pos.getElevation()) + " m");
			layer.addRenderable(sphere);
/*			
			PointPlacemark pp = new PointPlacemark(pos);
			pp.setLabelText(Integer.toString(index));
			pp.setValue(AVKey.DISPLAY_NAME, Integer.toString(index) + ") " + Integer.toString((int) pos.getElevation()) + " m");
			pp.setAltitudeMode(WorldWind.CLAMP_TO_GROUND);
			PointPlacemarkAttributes attrs = new PointPlacemarkAttributes();
			attrs.setLabelColor("ffffffff");
			attrs.setLineColor("ff0000ff");
			attrs.setUsePointAsDefaultImage(true);
			attrs.setScale(15.0);
			attrs.setLabelScale(0.8);
			attrs.setLabelOffset(Offset.fromFraction(0.3, 0.3));
			pp.setAttributes(attrs);
			pp.setEnableBatchPicking(false);
			layer.addRenderable(pp);
*/ 
			++index;
		}
	}

	private double getAbsoluteDistance(Position a, Position b) {
		Vec4 va = wwd.getModel().getGlobe().computePointFromPosition(a);
		Vec4 vb = wwd.getModel().getGlobe().computePointFromPosition(b);
		return va.distanceTo3(vb);
	}
	
	private final WorldWindow wwd;
	private final PropertyChangedListener updateVehicleListener;
	private final PropertyChangedListener updateRouteListener;
	private final String root;
	private final List<Position> track;
	private final static double MIN_TRACK_DISTANCE = 10;
	
	private Position lastFrom;
	private Position lastTo;
}
